﻿using System;
using System.ComponentModel;
using System.Diagnostics.Contracts;

namespace Cloak
{
	/// <summary>
	/// Creates instances of <see cref="IServiceProxy`1"/> by inferring the client type
	/// </summary>
	public static class ServiceProxy
	{
		/// <summary>
		/// Creates an instance of <see cref="IServiceProxy{TClient}"/> using the specified factory
		/// </summary>
		/// <typeparam name="TClient">The type of generated service client on which calls are made</typeparam>
		/// <param name="clientFactory">The function which creates instances of the generated client</param>
		/// <returns>A service proxy which creates instances of generated clients using the specified factory</returns>
		public static IServiceProxy<TClient> WithFactory<TClient>(Func<TClient> clientFactory)
		{
			Contract.Requires(clientFactory != null);

			return new ServiceProxy<TClient>(clientFactory);
		}
	}

	/// <summary>
	/// A proxy to a remote service which uses a generated client to make calls
	/// </summary>
	/// <typeparam name="TClient">The type of generated service client on which calls are made</typeparam>
	public sealed class ServiceProxy<TClient> : IServiceProxy<TClient>
	{
		private readonly Func<TClient> _clientFactory;
		
		/// <summary>
		/// Initializes a service proxy with the specified factory for making client instances
		/// </summary>
		/// <param name="clientFactory">The function which creates instances of the generated client</param>
		public ServiceProxy(Func<TClient> clientFactory)
		{
			Contract.Requires(clientFactory != null);

			_clientFactory = clientFactory;
		}

		/// <summary>
		/// Executes an asynchronous call to a remote service using an instance of a generated client
		/// </summary>
		/// <typeparam name="TCompletedEventArgs">
		/// The type of generated arguments consumed by handlers for the call
		/// (use <see cref="System.ComponentModel.AsyncCompletedEventArgs"/> for methods which return void)
		/// </typeparam>
		/// <param name="subscribeToCompletedHandler">The action which subscribes an event handler to the generated completed event on the client instance</param>
		/// <param name="execute">The action which makes the call on the client instance</param>
		/// <param name="onCompleted">The action which handles the results of a successful call</param>
		/// <param name="onError">The action which handles the results of a failed call</param>
		/// <param name="onCancelled">The action which handles the results of a cancelled call</param>
		public void ExecuteAsync<TCompletedEventArgs>(
			Action<TClient, EventHandler<TCompletedEventArgs>> subscribeToCompletedHandler,
			Action<TClient> execute,
			Action<TCompletedEventArgs> onCompleted = null,
			Action<Exception> onError = null,
			Action onCancelled = null)
			where TCompletedEventArgs : AsyncCompletedEventArgs
		{
			var client = _clientFactory();

			subscribeToCompletedHandler(
				client,
				(sender, args) =>
				{
					if(args.Error != null)
					{
						if(onError != null)
						{
							onError(args.Error);
						}
						else
						{
							throw args.Error;
						}
					}
					else if(args.Cancelled)
					{
						if(onCancelled != null)
						{
							onCancelled();
						}
						else
						{
							throw new OperationCancelledException();
						}
					}
					else
					{
						if(onCompleted != null)
						{
							onCompleted(args);
						}
					}
				});

			execute(client);
		}
	}
}